Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add client expiration check to update client before expired #20

Open
wants to merge 11 commits into
base: main
Choose a base branch
from

Conversation

ALPAC-4
Copy link
Collaborator

@ALPAC-4 ALPAC-4 commented Dec 17, 2024

Features

  • Client Expiration Prevention:
    • Update the client immediately after two-thirds of the trust period has passed.
    • Parse and feed the update client event.
    • Fix the feedUpdateClientEvent function.

Improvements

  • Query Optimization:
    • Use rpc.header instead of rest.block_info to reduce query costs and avoid the grpc: received message larger than max error.

Summary by CodeRabbit

  • New Features

    • Introduced a new method for retrieving clients that require updates.
    • Added functionality to handle UpdateClientEvent in event processing.
    • Implemented new event parsing functions for client updates.
    • Added a new function to categorize events based on attributes in block results.
    • Added a method to retrieve the latest consensus state for a given client ID.
  • Bug Fixes

    • Enhanced error handling and address matching logic in the getValidatorSet function.
  • Tests

    • Added a new test suite for the ClientController functionality.
    • Updated test cases for the parsePacketEvent function to align with new specifications.
    • Restructured test cases to focus on packet sending and fee processing.
  • Chores

    • Refactored references to table names for consistency across various controllers.

Copy link

coderabbitai bot commented Dec 17, 2024

Walkthrough

This pull request introduces enhancements to the client update mechanism across multiple components of the system. The changes focus on improving event parsing, client update tracking, and synchronization processes. A new UpdateClientEvent parsing function has been added, and the ClientController now includes a method to retrieve clients requiring updates. The modifications span several files, including database controllers, event parsers, workers, and update client message generation, creating a more robust and flexible client management system.

Changes

File Change Summary
src/db/controller/client.ts - Added getClientsToUpdate method to retrieve clients needing updates
- Enhanced feedUpdateClientEvent method with more precise update logic
- Added replaceClient method for replacing existing clients
- Added fetchClient method to streamline client data retrieval
src/db/controller/client.spec.ts - Added end-to-end test for client update mechanism
- Verifies getClientsToUpdate method functionality
src/lib/eventParser.ts - Introduced parseUpdateClientEvent and parseReplaceClientEvent functions
- Updated import statements to include UpdateClientEvent
src/lib/eventParser.spec.ts - Updated test cases for parsePacketEvent with new expected values
src/msgs/updateClient.ts - Modified getValidatorSet to use RPC header retrieval
- Updated proposer address handling
src/workers/chain.ts - Added handling for UpdateClientEvent in SyncWorker
- Updated fetchEvents method to return update client events
src/workers/wallet.ts - Refined client ID retrieval and update message generation
- Improved error handling and transaction processing
src/db/controller/channel.ts - Updated table name references to use ChannelController.tableName consistently
src/db/controller/connection.ts - Updated table name references to use ConnectionController.tableName consistently
src/db/controller/packet.ts - Updated table name references to use PacketController.tableName consistently
src/db/controller/packetFee.ts - Updated table name references to use PacketFeeController.tableName consistently
src/db/controller/syncInfo.ts - Updated table name references to use SyncInfoController.tableName consistently
src/db/controller/packet.spec.ts - Updated test suite to focus on packet sending instead of fee handling
- Replaced PacketFeeController with PacketController in tests
src/db/controller/packetFee.spec.ts - Transitioned test focus from packet sending to packet fee processing
- Updated expected database insertion checks for fee entries
src/lib/rpcClient.ts - Added decodeBlockResults function for processing RPC block results
- Defined new interfaces for structured JSON-RPC responses
src/lib/restClient.ts - Added lastConsensusState method to retrieve the latest consensus state
- Defined ConsState interface for consensus state response structure

Sequence Diagram

sequenceDiagram
    participant SyncWorker
    participant EventParser
    participant ClientController
    participant WalletWorker

    SyncWorker->>EventParser: Parse UpdateClientEvent
    EventParser-->>SyncWorker: Return parsed event
    SyncWorker->>ClientController: Feed update client event
    ClientController-->>SyncWorker: Update client information
    WalletWorker->>ClientController: Get clients to update
    ClientController-->>WalletWorker: Return clients needing updates
    WalletWorker->>WalletWorker: Generate update client messages
Loading

Poem

🐰 Hop, hop, through code so bright,
Client updates now take flight!
Events parsed with rabbit's might,
Synchronization shining light,
A dance of data, pure delight! 🌟


🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🔭 Outside diff range comments (2)
src/workers/wallet.ts (1)

Line range hint 177-188: Add error handling when fetching connection info

When retrieving connection information, if getConnection fails, it may cause an unhandled exception. Consider adding error handling to manage potential errors during the asynchronous operations.

Apply this diff to handle possible errors:

 await Promise.all(
   connections.map(async (connection) => {
     try {
       const connectionInfo = await ConnectionController.getConnection(
         this.chain.rest,
         this.chain.chainId,
         connection
       )
       connectionClientMap[connection] = connectionInfo.client_id
+    } catch (error) {
+      this.logger.error(`Failed to get connection info for ${connection}: ${error}`)
+      // Optionally, handle the error or skip this connection
+    }
   })
 )
src/lib/eventParser.spec.ts (1)

Line range hint 1-1: Add test coverage for parseUpdateClientEvent function.

The new parseUpdateClientEvent function lacks test coverage. Consider adding test cases to verify:

  • Successful parsing of update_client events
  • Handling of missing fields
  • Handling of malformed data

Example test structure:

test('update client parser', () => {
  // test successful parsing
  {
    const event = {
      type: 'update_client',
      attributes: [
        { key: 'client_id', value: 'client-1' },
        { key: 'header', value: 'header-data' },
        { key: 'consensus_heights', value: '1-1000' }
      ]
    }
    const result = parseUpdateClientEvent(event)
    expect(result).toEqual({
      clientId: 'client-1',
      header: 'header-data',
      consensusHeights: '1-1000'
    })
  }

  // test missing fields
  {
    const event = {
      type: 'update_client',
      attributes: [
        { key: 'client_id', value: 'client-1' }
      ]
    }
    expect(() => parseUpdateClientEvent(event)).toThrow()
  }
})
🧹 Nitpick comments (3)
src/db/controller/client.spec.ts (2)

8-8: Typo in comment

There's a duplicate word "set" in the comment. Please correct it for clarity.

Apply this diff:

 mockServers // to set set config file
-mockServers // to set set config file
+mockServers // to set config file

41-43: Use 'forEach' instead of 'map' for iteration with side effects

Since the result of map is not utilized, using forEach is more appropriate for performing operations with side effects.

Apply this diff:

-testClients.map((client) => {
+testClients.forEach((client) => {
   insert(DB, ClientController.tableName, client)
 })
src/lib/eventParser.ts (1)

132-142: LGTM: Well-structured event parsing implementation.

The parseUpdateClientEvent function follows the established pattern of other event parsers in the file. It correctly extracts the required fields using the existing find helper function.

However, consider adding error handling for missing required fields.

 export function parseUpdateClientEvent(event: Event): UpdateClientEvent {
   const clientId = find(event, 'client_id') as string
   const header = find(event, 'header') as string
   const consensusHeights = find(event, 'consensus_heights') as string
+  
+  if (!clientId || !header || !consensusHeights) {
+    throw new Error('Missing required fields in update client event')
+  }

   return {
     clientId,
     header,
     consensusHeights,
   }
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 294bff3 and 677fa94.

📒 Files selected for processing (7)
  • src/db/controller/client.spec.ts (1 hunks)
  • src/db/controller/client.ts (3 hunks)
  • src/lib/eventParser.spec.ts (1 hunks)
  • src/lib/eventParser.ts (2 hunks)
  • src/msgs/updateClient.ts (3 hunks)
  • src/workers/chain.ts (6 hunks)
  • src/workers/wallet.ts (4 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
src/db/controller/client.ts

[error] 71-71: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 107-107: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)

🔇 Additional comments (5)
src/db/controller/client.ts (1)

54-54: ⚠️ Potential issue

Add validation for parsing 'consensusHeights'

The parsing of event.consensusHeights assumes a specific format. If the format is unexpected, it may lead to runtime errors. Consider adding validation to handle potential parsing issues.

Apply this diff to add error handling:

 const consensusHeightsArray = event.consensusHeights.split(',')
 const revisionHeightParts = consensusHeightsArray[0].split('-')
+if (revisionHeightParts.length < 2) {
+  throw new Error('Invalid consensusHeights format')
+}
 const revisionHeight = parseInt(revisionHeightParts[1])

Likely invalid or redundant comment.

src/workers/wallet.ts (1)

306-307: Ensure 'msgs' array is populated before proceeding

The check if (msgs.length === 0) return is correctly placed after message generation to avoid unnecessary processing. Good job on optimizing the control flow.

src/lib/eventParser.ts (1)

2-7: LGTM: Clean import statement update.

The import statement is properly updated to include the new UpdateClientEvent type.

src/workers/chain.ts (2)

3-8: LGTM: Clean import updates.

The imports are properly organized to include the new UpdateClientEvent type.


269-272: LGTM: Clean event type handling.

The update_client event type is properly handled and parsed using the new parseUpdateClientEvent function.

src/db/controller/client.ts Outdated Show resolved Hide resolved
src/db/controller/client.ts Outdated Show resolved Hide resolved
src/msgs/updateClient.ts Outdated Show resolved Hide resolved
src/workers/chain.ts Outdated Show resolved Hide resolved
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🔭 Outside diff range comments (2)
src/msgs/updateClient.ts (1)

Line range hint 79-89: Improve error handling in the header retrieval loop.

The current implementation has a few issues:

  1. The error is only thrown after 5 retries, but the error message doesn't indicate the retry attempts.
  2. The delay between retries is fixed at 100ms without exponential backoff.

Consider this improved implementation:

-  let header = await chain.rpc.header(height).catch(() => undefined)
-  let count = 0
-  while (header === undefined) {
-    header = await chain.rpc.header(height).catch((e) => {
-      if (count > 5) {
-        throw e
-      }
-      return undefined
-    })
-    await delay(100)
-    count++
-  }
+  const MAX_RETRIES = 5;
+  const BASE_DELAY = 100;
+  let retries = 0;
+  
+  while (retries <= MAX_RETRIES) {
+    try {
+      const header = await chain.rpc.header(height);
+      if (header) return header;
+    } catch (e) {
+      if (retries === MAX_RETRIES) {
+        throw new Error(`Failed to fetch header after ${MAX_RETRIES} retries: ${e.message}`);
+      }
+      await delay(BASE_DELAY * Math.pow(2, retries));
+      retries++;
+    }
+  }
src/db/controller/packet.ts (1)

Line range hint 506-513: Critical: Incorrect table names used in delete operations.

Several delete operations are using the wrong table names, which will result in deleting records from the wrong tables:

  1. In feedAcknowledgePacketEvent:
    • Using tableNamePacketSend instead of tableNamePacketTimeout
    • Using tableNamePacketSend instead of tableNamePacketWriteAck
  2. In feedTimeoutPacketEvent:
    • Using tableNamePacketSend instead of tableNamePacketTimeout

Apply these fixes:

-      del<PacketTimeoutTable>(DB, PacketController.tableNamePacketSend, [
+      del<PacketTimeoutTable>(DB, PacketController.tableNamePacketTimeout, [

-      del<PacketWriteAckTable>(DB, PacketController.tableNamePacketSend, [
+      del<PacketWriteAckTable>(DB, PacketController.tableNamePacketWriteAck, [

-      del<PacketTimeoutTable>(DB, PacketController.tableNamePacketSend, [
+      del<PacketTimeoutTable>(DB, PacketController.tableNamePacketTimeout, [

Also applies to: 516-523, 570-577

🧹 Nitpick comments (4)
src/db/controller/client.ts (2)

2-2: Organize Imports for Readability

Consider ordering your imports to enhance readability. Typically, external libraries are imported first, followed by internal modules.


69-82: Avoid Magic Strings for Table Names

Using hard-coded table names can lead to maintenance issues. Consider defining table names as constants or using configuration files.

src/db/controller/packet.ts (2)

318-325: Consider using a transaction for atomic updates.

The resetPacketInProgress method updates multiple tables independently. If one update fails, it could leave the system in an inconsistent state.

Consider wrapping the updates in a transaction:

  public static resetPacketInProgress(db?: Database) {
    db = db ?? DB
+   db.transaction(() => {
      update<PacketSendTable>(db, PacketController.tableNamePacketSend, {
        in_progress: Bool.FALSE,
      })
      update<PacketTimeoutTable>(db, PacketController.tableNamePacketTimeout, {
        in_progress: Bool.FALSE,
      })
      update<PacketWriteAckTable>(db, PacketController.tableNamePacketWriteAck, {
        in_progress: Bool.FALSE,
      })
+   })()
  }

385-386: Consider using a transaction for atomic inserts.

The feedSendPacketEvent method inserts records into multiple tables independently. If one insert fails, it could leave the system in an inconsistent state.

Consider wrapping the inserts in a transaction:

    return () => {
+     DB.transaction(() => {
        insert(DB, PacketController.tableNamePacketSend, packetSend)
        insert(DB, PacketController.tableNamePacketTimeout, packetTimeout)
+     })()
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 677fa94 and e552835.

📒 Files selected for processing (7)
  • src/db/controller/channel.ts (8 hunks)
  • src/db/controller/client.ts (5 hunks)
  • src/db/controller/connection.ts (2 hunks)
  • src/db/controller/packet.ts (18 hunks)
  • src/db/controller/packetFee.ts (2 hunks)
  • src/db/controller/syncInfo.ts (4 hunks)
  • src/msgs/updateClient.ts (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/db/controller/channel.ts
🔇 Additional comments (10)
src/db/controller/client.ts (1)

91-91: Replace 'this' with Class Name in Static Method

In static methods, using this can be unclear since it refers to the class itself. Replace this with ClientController for clarity.

Apply this diff:

- const client = selectOne<ClientTable>(DB, this.tableName, [
+ const client = selectOne<ClientTable>(DB, ClientController.tableName, [
src/db/controller/connection.ts (2)

28-28: Replace 'this' with Class Name in Static Method

Using this in static methods can be confusing. Replace this.tableName with ConnectionController.tableName for clarity.


40-49: Consistent Reference to Class Name

Ensure consistency by replacing this.tableName with ConnectionController.tableName in the getConnection method.

Apply this diff:

- const connection = selectOne<ConnectionTable>(
-   DB,
-   this.tableName,
+ const connection = selectOne<ConnectionTable>(
+   DB,
+   ConnectionController.tableName,
    [
      {
        chain_id: chainId,
        connection_id: connectionId,
      },
    ]
  )
src/db/controller/packetFee.ts (2)

29-29: Replace 'this' with Class Name in Static Context

In static contexts, use the class name instead of this for clarity. Replace this.tableName with PacketFeeController.tableName.


55-55: Consistent Use of Class Name

Maintain consistency by using PacketFeeController.tableName instead of this.tableName.

src/db/controller/syncInfo.ts (4)

28-28: Replace 'this' with Class Name in Static Method

Replace this.tableName with SyncInfoController.tableName to clarify that the reference is to the class itself.


44-44: Consistent Reference to Class Name

For consistency, update this.tableName to SyncInfoController.tableName when inserting newSyncInfo.


56-58: Use Class Name Instead of 'this' in Static Method

In the getSyncInfos method, replace this.tableName with SyncInfoController.tableName for clarity.


78-92: Verify Logic in Sync Info Update

The update method may not correctly handle merging sync information when syncedHeight equals endHeight.

Consider testing scenarios where syncedHeight reaches endHeight to ensure sync information merges as expected.

src/msgs/updateClient.ts (1)

118-120: LGTM: Address comparison is now case-insensitive.

The updated implementation correctly converts both addresses to lowercase before comparison, preventing case-sensitivity issues.

src/db/controller/client.ts Show resolved Hide resolved
src/db/controller/client.ts Outdated Show resolved Hide resolved
src/db/controller/syncInfo.ts Show resolved Hide resolved
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (6)
src/db/controller/packet.spec.ts (1)

7-7: Typo in the describe block
Please correct "controler" to "controller" for consistent naming throughout the codebase.

Proposed fix:

-describe('channel controler', () => {
+describe('channel controller', () => {
src/lib/rpcClient.ts (1)

135-167: Consider using a for-loop instead of map for side effects.

Within rpcBlockResult.result.finalize_block_events.map((event) => { ... }), the map function is used for iteration rather than generating a transformed array. Switching to a for-loop improves clarity.

- rpcBlockResult.result.finalize_block_events.map((event) => {
+ for (const event of rpcBlockResult.result.finalize_block_events) {
src/db/controller/packetFee.spec.ts (1)

8-10: Typo in the test description.

Change "packet controler" to "packet controller" for consistency.

-describe('packet controler', () => {
+describe('packet controller', () => {
src/workers/wallet.ts (3)

Line range hint 177-189: Add error handling for connection retrieval.

The connection client mapping logic should handle potential failures when retrieving connection information.

 await Promise.all(
   connections.map(async (connection) => {
+    try {
       const connectionInfo = await ConnectionController.getConnection(
         this.chain.rest,
         this.chain.chainId,
         connection
       )
       connectionClientMap[connection] = connectionInfo.client_id
+    } catch (error) {
+      this.logger.error(`Failed to get connection info for ${connection}: ${error}`)
+      // Skip this connection but allow others to proceed
+    }
   })
 )

243-304: Clarify filtering comments and logic.

The comments suggest filtering expired clients, but the logic is actually checking for undefined client IDs in the connection map. This could be misleading for future maintenance.

 filteredSendPackets
   .filter(
     (packet) =>
-      connectionClientMap[packet.dst_connection_id] !== undefined
-    ) // filter expired client
+      connectionClientMap[packet.dst_connection_id] !== undefined
+    ) // filter packets with invalid connection-client mapping

Similar changes should be applied to the filtering logic in ackMsgs, timeoutMsgs, and channelOpenMsgs.


718-724: Consider moving utility function to a separate file.

The asyncFilter utility function is well-implemented but should be moved to a separate utilities file for better code organization and reusability.

Consider moving it to src/lib/utils.ts:

// src/lib/utils.ts
export async function asyncFilter<T>(
  array: T[],
  filter: (v: T) => Promise<boolean>
): Promise<T[]> {
  const filterRes = await Promise.all(array.map((v) => filter(v)))
  return array.filter((v, i) => filterRes[i])
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e552835 and efe572a.

📒 Files selected for processing (7)
  • src/db/controller/client.ts (5 hunks)
  • src/db/controller/packet.spec.ts (1 hunks)
  • src/db/controller/packetFee.spec.ts (1 hunks)
  • src/lib/eventParser.ts (2 hunks)
  • src/lib/rpcClient.ts (3 hunks)
  • src/workers/chain.ts (4 hunks)
  • src/workers/wallet.ts (7 hunks)
🧰 Additional context used
📓 Learnings (1)
src/db/controller/client.ts (2)
Learnt from: ALPAC-4
PR: initia-labs/rapid-relayer#20
File: src/db/controller/client.ts:54-66
Timestamp: 2024-12-17T07:59:34.765Z
Learning: In `src/db/controller/client.ts`, `consensusHeights` must exist and follow the `number-number` format as per the IBC-Go specification. If it doesn't exist or the format is incorrect, the process should throw an error and terminate.
Learnt from: ALPAC-4
PR: initia-labs/rapid-relayer#20
File: src/db/controller/client.ts:100-133
Timestamp: 2024-12-17T07:56:36.084Z
Learning: In the `getClientsToUpdate` method of `ClientController` in `src/db/controller/client.ts`, precise timing calculations are not necessary, and approximate times are acceptable for the client update conditions.
🔇 Additional comments (28)
src/db/controller/packet.spec.ts (4)

1-4: Imports look consistent
These imports and references to the PacketController and utilities appear aligned with the updated packet handling.


15-29: PacketEvent structure is correct
The send_packet event data structure aligning height, sequence, connection IDs, and timeouts is appropriate for validating packet transmissions.


67-68: Verification of test output
The final assertion correctly validates the database insertion for the send_packet flow. If you add more fields or logic in feedEvents, ensure this test remains synchronized.


47-48: Potential mismatch in destination connection
The insertion expects dst_connection_id: 'connection-2', but the event's connectionId is 'connection-1'. Verify whether there's logic that derives 'connection-2' or if it's a test setup mismatch.

✅ Verification successful

Let me gather more context about the test file to understand the test setup.


Let me check the test setup to see how the event is constructed.


Let me check how the connection ID is derived in the packet controller.


Let me check the connection controller to understand how the counterparty connection is derived.


Let me check the ConnectionController class directly.


Now I understand the flow. Looking at the implementation in PacketController.feedSendPacketEvent() and ConnectionController.getConnection(), the dst_connection_id is correctly derived from the counterparty connection information obtained through the REST client. The test is setting up a scenario where connection-1 on chain-1 is connected to connection-2 on chain-2, which is a valid IBC connection setup.

Test setup correctly reflects IBC connection topology
The test is valid. When a packet is sent from chain-1 using connection-1, it will be received on chain-2 through connection-2. The code correctly derives this by querying the connection's counterparty information through the REST client.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Searching how 'connection-2' is derived across the codebase:
rg -A 5 'connection-2'

Length of output: 5735


Script:

#!/bin/bash
# Get the full test context around the packet insertion
rg -B 10 -A 10 "dst_connection_id: 'connection-2'" src/db/controller/packet.spec.ts

Length of output: 712


Script:

#!/bin/bash
# Get the full test context including the event setup
rg -B 50 -A 10 "dst_connection_id: 'connection-2'" src/db/controller/packet.spec.ts

Length of output: 1840


Script:

#!/bin/bash
# Search for the packet controller implementation
ast-grep --pattern 'class PacketController {
  $$$
}'

Length of output: 37667


Script:

#!/bin/bash
# Search for the connection controller implementation
ast-grep --pattern 'getConnection($$$)' 

Length of output: 281


Script:

#!/bin/bash
# Search for the ConnectionController class
ast-grep --pattern 'class ConnectionController {
  $$$
}'

Length of output: 3056

src/lib/rpcClient.ts (2)

43-43: Use of decodeBlockResults is a solid step.

Calling decodeBlockResults(rawResponse) helps manage block result events more cleanly. No issues observed.


195-231: Interfaces look well structured.

The new interfaces (JsonRpcSuccessResponseGeneric, RpcBlockResultsResponse, etc.) improve type safety. Good job ensuring typed event attributes.

src/db/controller/packetFee.spec.ts (4)

1-6: Imports are correctly modified to reflect the shift to PacketFee.

Naming and types look cleaner and align well with the fee-based events.


12-44: Test logic aligns well with the structured event.

Replacing the mock events with a single parsePacketFeeEvent call is sound. The attributes cover the full range of fees.


46-49: Feed function creation is nicely modular.

Calling PacketFeeController.feedEvents decouples the setup from actual DB writes.


55-106: Well-defined fee expectations.

Your test coverage ensures correctness for each fee type (RECV, ACK, TIMEOUT), with correct amounts. This set of checks is thorough.

src/db/controller/client.ts (6)

2-2: Imports are consistent with the new usage of del and update.

No concerns here.


31-34: Client insertion logic is straightforward.

Returning the inserted client is helpful for immediate usage.


Line range hint 36-66: Refresh client with replaceClient is well-structured.

Rechecking expired packets via PacketController.resetPacketInProgress() is a good approach to ensure consistent state after client replacement.


101-114: Efficient update usage with update function.

The partial update pattern for revision_height and last_update_time is good.


123-123: Lazy creation of client with this.addClient.

This approach ensures new clients are inserted on the fly.


132-165: Two-thirds threshold approach is acceptable.

As per your prior note, approximate times are sufficient. This is well-aligned with the PR objective.

src/lib/eventParser.ts (1)

2-7: Additional import for UpdateClientEvent is consistent.

No issues.

src/workers/chain.ts (10)

3-8: Importing UpdateClientEvent is consistent with new usage.

No concerns here.


14-15: Parser functions for replace and update client events are properly referenced.

Nice job ensuring all event types are accounted for.


149-154: Combining events from multiple heights is excellent for batching.

Gathers updateClientEvents and replaceClientEvents cohesively.


160-168: Good separation of concerns for update events.

feedUpdateClientEvent outside the DB transaction is a recommended approach to isolate potential client state issues.


169-176: Replace client logic also stands alone.

Ensures upgradeClient and recoverClient calls remain independent.


231-232: New arrays for updateClientEvents and replaceClientEvents.

Data structure is clear and consistent with the rest of the event flow.


237-246: Flattening blockResult events is concise.

Collecting beginBlockEvents, tx events, and endBlockEvents in one array is succinct.


251-253: Initialization of arrays is well-labeled.

The explicit naming (packetFeeEvents, updateClientEvents, etc.) is easy to follow.


254-301: Event extraction logic is robust.

Your approach to parse each event type covers all relevant branches.


307-308: Properties updateClientEvents and replaceClientEvents are cleanly returned.

Enables the calling function to destructure them easily.

src/workers/wallet.ts (1)

191-194: Verify trust period threshold in ClientController.

The PR objectives mention updating clients after 2/3 of the trust period, but this threshold isn't visible in the current code. Let's verify the implementation in ClientController.

✅ Verification successful

Trust period threshold is correctly implemented at 2/3 (0.666) in ClientController

The verification confirms that the 2/3 trust period threshold is properly implemented in src/db/controller/client.ts:

if (
  client.last_update_time + client.trusting_period * 0.666 <
  currentTimestamp
) {
  return true
}

The code checks if:

  1. The client is not expired: client.last_update_time + client.trusting_period < currentTimestamp
  2. The client needs update: When more than 66.6% (2/3) of the trusting period has elapsed
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if ClientController implements the 2/3 trust period threshold

# Search for the getClientsToUpdate implementation
ast-grep --pattern 'getClientsToUpdate($$$) {
  $$$
}'

# Search for any trust period calculations
rg -A 5 'trust.*period|2/3|trusting.*period'

Length of output: 3974

src/db/controller/client.ts Outdated Show resolved Hide resolved
src/lib/eventParser.ts Outdated Show resolved Hide resolved
src/workers/wallet.ts Outdated Show resolved Hide resolved
src/workers/wallet.ts Outdated Show resolved Hide resolved
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
src/workers/wallet.ts (1)

204-217: ⚠️ Potential issue

Fix typo in function name and improve error handling.

The function name has a typo and lacks error handling for client retrieval.

-const fileterExpiredClient = async (clientId: string) => {
+const filterExpiredClient = async (clientId: string) => {
   const currentTimestamp = new Date().valueOf() / 1000

+  try {
     const client = await ClientController.getClient(
       this.chain.rest,
       this.chain.chainId,
       clientId
     )

     return (
-      client.last_update_time + client.trusting_period < currentTimestamp
+      Math.floor(client.last_update_time + client.trusting_period) < Math.floor(currentTimestamp)
     )
+  } catch (error) {
+    this.logger.error(`Failed to get client info for ${clientId}: ${error}`)
+    return true // Consider client as expired if we can't verify its state
+  }
 }
🧹 Nitpick comments (4)
src/workers/wallet.ts (4)

190-194: Add error handling for client update check.

The call to getClientsToUpdate should include error handling to gracefully handle potential failures.

-      const clientsToUpdate = ClientController.getClientsToUpdate(
+      const clientsToUpdate = await ClientController.getClientsToUpdate(
         this.chain.chainId,
         counterpartyChainIdsWithFeeFilter.map((f) => f.chainId)
-      )
+      ).catch(error => {
+        this.logger.error(`Failed to get clients to update: ${error}`);
+        return [];
+      });

243-301: Improve code organization and type safety in message generation.

The message filtering and generation code could benefit from the following improvements:

  1. Extract common filtering logic to reduce code duplication
  2. Add type safety for undefined checks
+interface PacketFilter {
+  connectionId: string;
+  clientId: string;
+  height: Height;
+}
+
+function filterExpiredClientPackets<T extends { connection_id: string }>(
+  packets: T[],
+  connectionClientMap: Record<string, string>,
+  updateClientMsgs: Record<string, { msg: MsgUpdateClient; height: Height }>
+): PacketFilter[] {
+  return packets
+    .filter(packet => {
+      const clientId = connectionClientMap[packet.connection_id];
+      return updateClientMsgs[clientId] !== undefined;
+    })
+    .map(packet => ({
+      connectionId: packet.connection_id,
+      clientId: connectionClientMap[packet.connection_id],
+      height: updateClientMsgs[connectionClientMap[packet.connection_id]].height
+    }));
+}

// Example usage for recv packets:
const recvPacketMsgs = await Promise.all(
  filterExpiredClientPackets(
    filteredSendPackets,
    connectionClientMap,
    updateClientMsgs
  ).map(({ height }) =>
    this.workerController.generateRecvPacketMsg(
      packet,
      height,
      this.address()
    )
  )
);

724-730: Add documentation and error handling to asyncFilter.

The helper function could benefit from JSDoc documentation and error handling for individual filter operations.

+/**
+ * Filters an array asynchronously using the provided filter function.
+ * @template T The type of elements in the array
+ * @param {T[]} array The array to filter
+ * @param {(v: T) => Promise<boolean>} filter The async filter function
+ * @returns {Promise<T[]>} A promise that resolves to the filtered array
+ */
 async function asyncFilter<T>(
   array: T[],
   filter: (v: T) => Promise<boolean>
 ): Promise<T[]> {
-  const filterRes = await Promise.all(array.map((v) => filter(v)))
+  const filterRes = await Promise.all(
+    array.map((v) =>
+      filter(v).catch((error) => {
+        console.error(`Filter operation failed: ${error}`);
+        return false;
+      })
+    )
+  );
   return array.filter((v, i) => filterRes[i])
 }

352-352: Consider implementing a rate-limiting strategy.

The worker processes messages in batches but doesn't implement a clear backoff strategy. Consider adding rate limiting to prevent overwhelming the chain's RPC endpoints during high load.

Some suggestions:

  1. Implement exponential backoff when errors occur
  2. Add configurable delays between batches based on chain load
  3. Consider using a token bucket algorithm for rate limiting

Would you like me to provide a detailed implementation for any of these approaches?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between efe572a and b621415.

📒 Files selected for processing (1)
  • src/workers/wallet.ts (7 hunks)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/lib/restClient.ts (1)

58-62: Consider adding robust error handling for consensus state retrieval.

Currently, if the endpoint is unavailable or returns an unexpected format, the method might throw an unhandled exception. You may want to add a try/catch block or a validation step to ensure the response meets the expected schema before returning it.

src/db/controller/client.ts (1)

142-166: Consider handling invalid or missing data in fetchClient.

If rest.ibc.getClientState(clientId) or rest.ibc.lastConsensusState(clientId) returns invalid data or fails unexpectedly, you risk inserting partially parsed data into the table. Consider validating or wrapping these calls in try/catch to ensure data integrity.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b621415 and 5beefd1.

📒 Files selected for processing (2)
  • src/db/controller/client.ts (4 hunks)
  • src/lib/restClient.ts (2 hunks)
🧰 Additional context used
📓 Learnings (1)
src/db/controller/client.ts (2)
Learnt from: ALPAC-4
PR: initia-labs/rapid-relayer#20
File: src/db/controller/client.ts:54-66
Timestamp: 2024-12-17T07:59:34.765Z
Learning: In `src/db/controller/client.ts`, `consensusHeights` must exist and follow the `number-number` format as per the IBC-Go specification. If it doesn't exist or the format is incorrect, the process should throw an error and terminate.
Learnt from: ALPAC-4
PR: initia-labs/rapid-relayer#20
File: src/db/controller/client.ts:100-133
Timestamp: 2024-12-17T07:56:36.084Z
Learning: In the `getClientsToUpdate` method of `ClientController` in `src/db/controller/client.ts`, precise timing calculations are not necessary, and approximate times are acceptable for the client update conditions.
🔇 Additional comments (4)
src/lib/restClient.ts (1)

113-127: Interface definition looks consistent with IBC-Go data structures.

No issues found. The design accurately reflects the consensus state fields.

src/db/controller/client.ts (3)

24-37: Check for potential errors in client replacement flow.

When replacing a client, consider validating that the fetchClient call succeeds. If it fails (e.g., the client doesn't exist in remote state), you could throw or handle the error to avoid inserting invalid data.


108-140: Implementation of approximate two-thirds check looks good.

The getClientsToUpdate method correctly filters out expired clients and returns those whose last_update_time meets the two-thirds threshold. This aligns with the PR objectives to update clients before they expire.


61-73: ⚠️ Potential issue

Add format validation for event.consensusHeights.

The code implicitly assumes the string has the format "abc,def" and that abc.split('-')[1] always exists. According to the user’s learnings, if consensusHeights is missing or incorrectly formatted, the process must terminate. Consider adding an explicit format check before parsing to avoid runtime errors:

- const revisionHeight = parseInt(event.consensusHeights.split(',')[0].split('-')[1])
+ const consensusHeightsArr = event.consensusHeights.split(',')
+ if (!consensusHeightsArr.length) {
+   throw new Error('Invalid consensusHeights format. Expected "<revision>-<height>" in a comma-separated list')
+ }
+ const [revision, height] = consensusHeightsArr[0].split('-')
+ if (!revision || !height) {
+   throw new Error('Invalid consensusHeights format. Expected "<revision>-<height>"')
+ }
+ const revisionHeight = parseInt(height, 10)

Likely invalid or redundant comment.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/workers/wallet.ts (1)

190-203: Ensure parallel retrievals won't exceed resource constraints.
getClientsToUpdate and subsequent array operations might open too many parallel DB calls in large-scale scenarios. Consider adding concurrency limits or batching if performance becomes an issue.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 52b0c7a and 434d732.

📒 Files selected for processing (1)
  • src/workers/wallet.ts (7 hunks)
🔇 Additional comments (11)
src/workers/wallet.ts (11)

23-23: Import for client updates is good to see.
Enables direct usage of the ClientController for retrieving and updating client data.


177-177: Consider validating empty connections.
If connections is empty, the subsequent mapping of connection infos is skipped. Just ensure there's no undesired behavior if connections is an empty array.


204-221: Typo in function name “fileterExpiredClient”.
It’s currently misspelled as “fileterExpiredClient” and should be “filterExpiredClient”. This was flagged previously.


231-231: Generate update client messages in a controlled loop.
While the single Promise.all usage here is acceptable, keep an eye on potential large concurrency issues if filteredClientIds grows in size.


243-259: Avoid message generation for invalid or expired client references.
The inline filtering step correctly prevents message generation for unknown or expired clients. Good job ensuring data integrity before sending.


264-280: Similar filtering logic present in Ack messages.
The approach matches the preceding pattern, verifying the presence of valid client update messages. This consistency is beneficial for maintainability.


285-301: Timeout packets are similarly filtered.
Your approach ensures that only valid, unexpired clients generate timeout messages.


308-310: Filtering channel events based on client validity.
Ensures that invalid clients do not produce channel open messages. Great consistency.


352-353: Conditionally short-circuit with msgs.length === 0.
This helps prevent unnecessary transaction calls.


720-720: Ensure defensive checks when returning filtered events.
Line 720 returns the filtered event array, which can be empty or partially pruned. It’s correct, but keep an eye on whether any remaining logic needs to handle emptiness before usage.


724-730: Utility asyncFilter is well-implemented.
This helper function is straightforward and improves readability.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (5)
src/db/controller/client.ts (2)

68-72: Validate revisionHeight parsing.
If parseInt fails, it returns NaN. Consider adding additional checks for isNaN(revisionHeight) to avoid silently overwriting client.revision_height with NaN.


81-94: Efficient database updates.
Your update<ClientTable> approach is clear. Consider adding error handling or a rollback strategy if the update fails to ensure consistency.

src/workers/wallet.ts (3)

204-218: Expiration check logic.
Your check for client.last_update_time + client.trusting_period > currentTimestamp is reversed from the typical "Is it expired?" approach. Here, true means "not expired" so the naming or logic might be confusing.


231-231: Error handling for generateMsgUpdateClient.
If generateMsgUpdateClient fails for any reason, you might end up with an unhandled situation. Consider wrapping each call in a try-catch to handle potential REST or parsing errors individually.


243-249: Filtering for expired clients.
.filter(...) // filter expired client might omit the reason behind the filter in code-level comments. Consider clarifying so future devs understand the condition precisely.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 434d732 and 8f164cc.

📒 Files selected for processing (3)
  • src/db/controller/client.ts (4 hunks)
  • src/lib/eventParser.ts (2 hunks)
  • src/workers/wallet.ts (7 hunks)
🧰 Additional context used
📓 Learnings (1)
src/db/controller/client.ts (2)
Learnt from: ALPAC-4
PR: initia-labs/rapid-relayer#20
File: src/db/controller/client.ts:54-66
Timestamp: 2024-12-17T07:59:34.765Z
Learning: In `src/db/controller/client.ts`, `consensusHeights` must exist and follow the `number-number` format as per the IBC-Go specification. If it doesn't exist or the format is incorrect, the process should throw an error and terminate.
Learnt from: ALPAC-4
PR: initia-labs/rapid-relayer#20
File: src/db/controller/client.ts:100-133
Timestamp: 2024-12-17T07:56:36.084Z
Learning: In the `getClientsToUpdate` method of `ClientController` in `src/db/controller/client.ts`, precise timing calculations are not necessary, and approximate times are acceptable for the client update conditions.
🔇 Additional comments (13)
src/db/controller/client.ts (6)

2-2: Good import organization.
Importing del, insert, select, selectOne, update from ../utils is consistent, and it clearly indicates the CRUD operations used throughout the file.


17-22: Streamlined client addition logic.
Using fetchClient within addClient simplifies the process and ensures that the client object is consistently constructed.


24-37: Ensure error handling for replaceClient.
While replaceClient fetches, deletes, and reinserts the client, consider double-checking for potential race conditions or partial failures (e.g., fetch succeeds but delete fails). If partial errors occur, operations may leave inconsistent states.


75-78: Check null fields in the header.
header.signedHeader?.header?.time.seconds might be undefined in some edge cases. Verify upstream that header is always correctly populated.


112-145: Client filtering logic is acceptable.
The logic that excludes expired clients and includes near-expiry clients follows the approximate approach that ALPAC-4 mentioned was acceptable. Ensure that beneficial off-by-one edge cases are covered by tests.


147-171: Code clarity for retrieval logic.
The fetchClient method is well-structured. It provides a single source of truth for pulling client data from REST. Good job ensuring the consistent approach for client_id and chain_id.

src/lib/eventParser.ts (1)

144-151: Falling back to client_id.
In parseReplaceClientEvent, falling back to subject_client_id or client_id is valid. Check that an empty string isn't returned by find if the attribute is missing. Throw an error if no valid client ID is found.

src/workers/wallet.ts (6)

23-23: Centralizing imports.
Importing ClientController in wallet.ts is consistent with the usage for client updates. This also clarifies dependencies among modules.


Line range hint 177-203: Prevent concurrency issues with multiple calls.
Multiple concurrent calls to connectionClientMap population might need synchronization if the environment is multi-threaded or event-driven. Otherwise, partial writes could cause stale data.


219-221: Async filter usage is neat.
asyncFilter is a succinct way to handle concurrency. Well done simplifying the logic for validating multiple clients.


308-310: Sorting channel close first is good.
Using .sort((a, b) => b.state - a.state) ensures close events are processed. Confirm that this numeric ordering is stable for all channel states. For instance, CLOSE is bigger or smaller in the enum order than INIT, TRYOPEN, etc.


352-353: Early return.
Exiting when msgs.length === 0 is a good step to avoid unnecessary overhead. Confirm that partial updates (e.g., some clients are updated, but no new messages are created) won't break the workflow.


720-730: asyncFilter is a handy utility.
The asyncFilter utility function is well-implemented; it systematically checks each item in parallel. This is efficient and shortens code.

src/db/controller/client.ts Outdated Show resolved Hide resolved
src/lib/eventParser.ts Show resolved Hide resolved
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (4)
src/db/controller/client.ts (4)

17-22: Check for database insertion errors.
While this insertion is straightforward, consider wrapping insert in a try-catch block or verifying that the database call succeeds to handle potential DB errors more gracefully.


24-37: Ensure robust error handling in replaceClient.
When deleting and re-inserting the client, if either DB call fails, it could corrupt or partially remove the record. Consider transactional operations or a rollback strategy to maintain DB consistency.


81-94: Consider logging or returning update success status.
Right now, the function calls update silently. Providing some form of acknowledgment or logging would improve observability to confirm a successful DB update.


103-103: Handle potential fetchClient errors.
This usage is fine, but keep in mind that this.addClient(...) could fail if it can’t fetch or insert. A try-catch approach here could stop repetitive DB attempts in infinite loops if rest calls fail.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8f164cc and a48fc2f.

📒 Files selected for processing (1)
  • src/db/controller/client.ts (4 hunks)
🧰 Additional context used
📓 Learnings (1)
src/db/controller/client.ts (2)
Learnt from: ALPAC-4
PR: initia-labs/rapid-relayer#20
File: src/db/controller/client.ts:54-66
Timestamp: 2024-12-17T07:59:34.765Z
Learning: In `src/db/controller/client.ts`, `consensusHeights` must exist and follow the `number-number` format as per the IBC-Go specification. If it doesn't exist or the format is incorrect, the process should throw an error and terminate.
Learnt from: ALPAC-4
PR: initia-labs/rapid-relayer#20
File: src/db/controller/client.ts:100-133
Timestamp: 2024-12-17T07:56:36.084Z
Learning: In the `getClientsToUpdate` method of `ClientController` in `src/db/controller/client.ts`, precise timing calculations are not necessary, and approximate times are acceptable for the client update conditions.
🔇 Additional comments (7)
src/db/controller/client.ts (7)

2-2: Import usage looks good.
Importing del, insert, select, selectOne, update centralizes CRUD operations and promotes consistency.


7-7: PacketController import is appropriate.
Bringing in PacketController precisely where needed keeps dependencies uncluttered.


61-66: Consensus heights format validation is correct.
This new check effectively guards against invalid consensusHeights strings and aligns with the IBC-Go spec requirement of a number-number format.


70-72: Revision height logic seems appropriate.
Only updating when revisionHeight exceeds the current client.revision_height is correct. This ensures monotonic revision progression.


75-78: Time comparison is accurate.
Using header.signedHeader.header.time.seconds ensures we only update if there's a strictly newer timestamp. This is aligned with typical block timestamp validation.


113-145: Approximate timing logic is acceptable.
As per your clarifications, the approximate check at two-thirds of the trusting period is sufficient for the use case, ensuring timely updates without being overly strict.


147-171: Successful integration of fetchClient method.
Centralizing client data retrieval in fetchClient fosters reusability. The code is clean, and the usage of state plus consState is well-organized.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant